#include "SPEmitterMesh.h"
#include "SPCamera.h"
#pragma warning( disable: 4995 )
#include <fstream>
using namespace std;
#pragma warning( default: 4995 )

///------------------------------------------------------------
// SPObjLoader
///------------------------------------------------------------
namespace SP
{
	SPOBJLoader::SPOBJLoader( const char* cstrFileName )
	{
		HRESULT hr;
		WCHAR wstr[MAX_PATH];
		MultiByteToWideChar( CP_ACP, 0, cstrFileName, -1, wstr, MAX_PATH );
		V( LoadGeometryFromOBJ( wstr ) );
	}

	SPOBJLoader::SPOBJLoader( const WCHAR* wstrFileName )
	{
		HRESULT hr;
		V( LoadGeometryFromOBJ( wstrFileName ) );
	}

	SPOBJLoader::~SPOBJLoader(  )
	{
		for ( UINT iMaterial = 0; iMaterial < m_Materials.size(  ); ++iMaterial )
		{
			Material *pMaterial = m_Materials.at( iMaterial );
			SAFE_DELETE( pMaterial );
		}
		m_Materials.clear(  );
		m_Vertices.clear(  );
		m_Indices.clear(  );
		m_Attributes.clear(  );
	}

	HRESULT SPOBJLoader::LoadGeometryFromOBJ( const WCHAR* strFileName )
	{
		WCHAR strMaterialFilename[MAX_PATH] = {0};
		WCHAR wstr[MAX_PATH];
		char str[MAX_PATH];
		HRESULT hr;

		// Find the file
		wcscpy_s( wstr, strFileName );
		WideCharToMultiByte( CP_ACP, 0, wstr, -1, str, MAX_PATH, NULL, NULL );

		// Store the directory where the mesh was found
		wcscpy_s( m_strMediaDir, MAX_PATH - 1, wstr );
		WCHAR* pch = wcsrchr( m_strMediaDir, L'\\' );
		if ( pch )
			*pch = NULL;

		// Create temporary storage for the input data
		vector <XMFLOAT3> Positions;
		vector <XMFLOAT2> TexCoords;
		vector <XMFLOAT3> Normals;

		// The first subset uses the default material
		Material* pMaterial = new Material(  );
		if ( pMaterial == NULL )
			return E_OUTOFMEMORY;

		InitMaterial( pMaterial );
		wcscpy_s( pMaterial->strName, MAX_PATH - 1, L"default" );
		m_Materials.push_back( pMaterial );

		DWORD dwCurSubset = 0;

		// File input
		WCHAR strCommand[256] = {0};
		wifstream InFile( strFileName );
		if ( !InFile )
			return DXTRACE_ERR( L"wifstream::open", E_FAIL );

		while ( true )
		{
			InFile >> strCommand;
			if ( !InFile )
				break;

			if ( 0 == wcscmp( strCommand, L"#" ) )
			{
				// Comment
			}
			else if ( 0 == wcscmp( strCommand, L"v" ) )
			{
				// Vertex Position
				float x, y, z;
				InFile >> x >> y >> z;
				Positions.push_back( XMFLOAT3( x, y, z ) );
			}
			else if ( 0 == wcscmp( strCommand, L"vt" ) )
			{
				// Vertex TexCoord
				float u, v;
				InFile >> u >> v;
				TexCoords.push_back( XMFLOAT2( u, v ) );
			}
			else if ( 0 == wcscmp( strCommand, L"vn" ) )
			{
				// Vertex Normal
				float x, y, z;
				InFile >> x >> y >> z;
				Normals.push_back( XMFLOAT3( x, y, z ) );
			}
			else if ( 0 == wcscmp( strCommand, L"f" ) )
			{
				// Face
				UINT iPosition, iTexCoord, iNormal;
				VERTEX vertex;

				for ( UINT iFace = 0; iFace < 3; iFace++ )
				{
					ZeroMemory( &vertex, sizeof( VERTEX ) );

					// OBJ format uses 1-based arrays
					InFile >> iPosition;
					vertex.position = Positions[iPosition - 1];

					if ( '/' == InFile.peek(  ) )
					{
						InFile.ignore(  );

						if ( '/' != InFile.peek(  ) )
						{
							// Optional texture coordinate
							InFile >> iTexCoord;
							vertex.texcoord = TexCoords[iTexCoord - 1];
						}

						if ( '/' == InFile.peek(  ) )
						{
							InFile.ignore(  );

							// Optional vertex normal
							InFile >> iNormal;
							vertex.normal = Normals[iNormal - 1];
						}
					}

					// If a duplicate vertex doesn't exist, add this vertex to the Vertices
					// list. Store the index in the Indices array. The Vertices and Indices
					// lists will eventually become the Vertex Buffer and Index Buffer for
					// the mesh.
					DWORD index = AddVertex( iPosition, &vertex );
					if ( index == ( DWORD )-1 )
						return E_OUTOFMEMORY;

					m_Indices.push_back( index );
				}
				m_Attributes.push_back( dwCurSubset );
			}
			else if ( 0 == wcscmp( strCommand, L"mtllib" ) )
			{
				// Material library
				InFile >> strMaterialFilename;
			}
			else if ( 0 == wcscmp( strCommand, L"usemtl" ) )
			{
				// Material
				WCHAR strName[MAX_PATH] = {0};
				InFile >> strName;

				bool bFound = false;
				for ( UINT iMaterial = 0; iMaterial < m_Materials.size(  ); iMaterial++ )
				{
					Material* pCurMaterial = m_Materials.at( iMaterial );
					if ( 0 == wcscmp( pCurMaterial->strName, strName ) )
					{
						bFound = true;
						dwCurSubset = iMaterial;
						break;
					}
				}

				if ( !bFound )
				{
					pMaterial = new Material(  );
					if ( pMaterial == NULL )
						return E_OUTOFMEMORY;

					dwCurSubset = m_Materials.size(  );

					InitMaterial( pMaterial );
					wcscpy_s( pMaterial->strName, MAX_PATH - 1, strName );

					m_Materials.push_back( pMaterial );
				}
			}
			else
			{
				// Unimplemented or unrecognized command
			}

			InFile.ignore( 1000, '\n' );
		}

		// Cleanup
		InFile.close(  );
		DeleteCache(  );

		// If an associated material file was found, read that in as well.
		if ( strMaterialFilename[0] )
		{
			V_RETURN( LoadMaterialsFromMTL( strMaterialFilename ) );
		}

		return S_OK;

	}

	DWORD SPOBJLoader::AddVertex( UINT hash, VERTEX* pVertex )
	{
		// If this vertex doesn't already exist in the Vertices list, create a new entry.
		// Add the index of the vertex to the Indices list.
		bool bFoundInList = false;
		DWORD index = 0;

		// Since it's very slow to check every element in the vertex list, a hashtable stores
		// vertex indices according to the vertex position's index as reported by the OBJ file
		if ( ( UINT )m_VertexCache.size(  ) > hash )
		{
			CacheEntry* pEntry = m_VertexCache.at( hash );
			while( pEntry != NULL )
			{
				VERTEX* pCacheVertex = m_Vertices.data(  ) + pEntry->index;

				// If this vertex is identical to the vertex already in the list, simply
				// point the index buffer to the existing vertex
				if ( 0 == memcmp( pVertex, pCacheVertex, sizeof( VERTEX ) ) )
				{
					bFoundInList = true;
					index = pEntry->index;
					break;
				}

				pEntry = pEntry->pNext;
			}
		}

		// Vertex was not found in the list. Create a new entry, both within the Vertices list
		// and also within the hashtable cache
		if ( !bFoundInList )
		{
			// Add to the Vertices list
			index = m_Vertices.size(  );
			m_Vertices.push_back( *pVertex );

			// Add this to the hashtable
			CacheEntry* pNewEntry = new CacheEntry;
			if ( pNewEntry == NULL )
				return ( DWORD )-1;

			pNewEntry->index = index;
			pNewEntry->pNext = NULL;

			// Grow the cache if needed
			while( ( UINT )m_VertexCache.size(  ) <= hash )
			{
				m_VertexCache.push_back( NULL );
			}

			// Add to the end of the linked list
			CacheEntry* pCurEntry = m_VertexCache.at( hash );
			if ( pCurEntry == NULL )
			{
				// This is the head element
				m_VertexCache[hash] = pNewEntry;
			}
			else
			{
				// Find the tail
				while( pCurEntry->pNext != NULL )
				{
					pCurEntry = pCurEntry->pNext;
				}

				pCurEntry->pNext = pNewEntry;
			}
		}

		return index;
	}

	void SPOBJLoader::DeleteCache(  )
	{
		// Iterate through all the elements in the cache and subsequent linked lists
		for ( UINT i = 0; i < m_VertexCache.size(  ); i++ )
		{
			CacheEntry* pEntry = m_VertexCache.at( i );
			while( pEntry != NULL )
			{
				CacheEntry* pNext = pEntry->pNext;
				SAFE_DELETE( pEntry );
				pEntry = pNext;
			}
		}

		m_VertexCache.clear(  );
	}

	HRESULT SPOBJLoader::LoadMaterialsFromMTL( const WCHAR* strFileName )
	{
		// Set the current directory based on where the mesh was found
		WCHAR wstrOldDir[MAX_PATH] = {0};
		GetCurrentDirectory( MAX_PATH, wstrOldDir );
		SetCurrentDirectory( m_strMediaDir );

		// Find the file
		WCHAR str[MAX_PATH];
		char cstr[MAX_PATH];

		// Find mtl file
		wcscpy_s( str, m_strMediaDir );
		wcscat_s( str, L"\\" );
		wcscat_s( str, strFileName );
		WideCharToMultiByte( CP_ACP, 0, str, -1, cstr, MAX_PATH, NULL, NULL );

		// Restore the original current directory
		SetCurrentDirectory( wstrOldDir );

		// File input
		WCHAR strCommand[256] = {0};
		wifstream InFile( cstr );
		if ( !InFile )
			return DXTRACE_ERR( L"wifstream::open", E_FAIL );

		Material* pMaterial = NULL;

		for ( ; ; )
		{
			InFile >> strCommand;
			if ( !InFile )
				break;

			if ( 0 == wcscmp( strCommand, L"newmtl" ) )
			{
				// Switching active materials
				WCHAR strName[MAX_PATH] = {0};
				InFile >> strName;

				pMaterial = NULL;
				for ( UINT i = 0; i < m_Materials.size(  ); i++ )
				{
					Material* pCurMaterial = m_Materials.at( i );
					if ( 0 == wcscmp( pCurMaterial->strName, strName ) )
					{
						pMaterial = pCurMaterial;
						break;
					}
				}
			}

			// The rest of the commands rely on an active material
			if ( pMaterial == NULL )
				continue;

			if ( 0 == wcscmp( strCommand, L"#" ) )
			{
				// Comment
			}
			else if ( 0 == wcscmp( strCommand, L"Ka" ) )
			{
				// Ambient color
				float r, g, b;
				InFile >> r >> g >> b;
				pMaterial->vAmbient = XMFLOAT3( r, g, b );
			}
			else if ( 0 == wcscmp( strCommand, L"Kd" ) )
			{
				// Diffuse color
				float r, g, b;
				InFile >> r >> g >> b;
				pMaterial->vDiffuse = XMFLOAT3( r, g, b );
			}
			else if ( 0 == wcscmp( strCommand, L"Ks" ) )
			{
				// Specular color
				float r, g, b;
				InFile >> r >> g >> b;
				pMaterial->vSpecular = XMFLOAT3( r, g, b );
			}
			else if ( 0 == wcscmp( strCommand, L"d" ) ||
				0 == wcscmp( strCommand, L"Tr" ) )
			{
				// Alpha
				InFile >> pMaterial->fAlpha;
			}
			else if ( 0 == wcscmp( strCommand, L"Ns" ) )
			{
				// Shininess
				int nShininess;
				InFile >> nShininess;
				pMaterial->nShininess = nShininess;
			}
			else if ( 0 == wcscmp( strCommand, L"illum" ) )
			{
				// Specular on/off
				int illumination;
				InFile >> illumination;
				pMaterial->bSpecular = ( illumination == 2 );
			}
			else if ( 0 == wcscmp( strCommand, L"map_Kd" ) )
			{
				// Texture
				InFile >> str;
				wcscpy_s( pMaterial->strTexture, m_strMediaDir );
				wcscat_s( pMaterial->strTexture, L"\\" );
				wcscat_s( pMaterial->strTexture, str );
			}
			else
			{
				// Unimplemented or unrecognized command
			}

			InFile.ignore( 1000, L'\n' );
		}

		InFile.close(  );

		return S_OK;
	}

	void SPOBJLoader::InitMaterial( Material* pMaterial )
	{
		ZeroMemory( pMaterial, sizeof( Material ) );

		pMaterial->vAmbient = XMFLOAT3( 0.2f, 0.2f, 0.2f );
		pMaterial->vDiffuse = XMFLOAT3( 0.8f, 0.8f, 0.8f );
		pMaterial->vSpecular = XMFLOAT3( 1.0f, 1.0f, 1.0f );
		pMaterial->nShininess = 0;
		pMaterial->fAlpha = 1.0f;
		pMaterial->bSpecular = false;
	}
}

///------------------------------------------------------------
// SPStaticMesh
///------------------------------------------------------------
namespace SP
{
	SPStaticMesh::SPStaticMesh()
	{
		m_pVertexBuffer = NULL;
		m_pIndexBuffer = NULL;

		m_pIndices = NULL;
		m_pVertices = NULL;

		m_Subsets.clear();
		m_Materials.clear();
	}

	SPStaticMesh::SPStaticMesh( SPOBJLoader* pOBJLoader )
	{
		SPStaticMesh();
		HRESULT hr;
		V( Create( pOBJLoader ) );
	}

	SPStaticMesh::~SPStaticMesh()
	{

	}

	HRESULT SPStaticMesh::CreateBuffer()
	{
		HRESULT hr;

		// VB
		D3D11_BUFFER_DESC bd;
		ZeroMemory( &bd, sizeof(bd) );
		bd.ByteWidth = (UINT)( sizeof(SPOBJLoader::VERTEX) * m_nVertexCount );
		bd.Usage = D3D11_USAGE_DEFAULT;
		bd.BindFlags = D3D11_BIND_VERTEX_BUFFER;
		bd.CPUAccessFlags = 0;
		bd.MiscFlags = 0;
		D3D11_SUBRESOURCE_DATA InitData;
		ZeroMemory( &InitData, sizeof(InitData) );
		InitData.pSysMem = m_pVertices;
		V_RETURN( SPGetD3D11Device()->CreateBuffer(&bd, &InitData, &m_pVertexBuffer) );

		// IB
		bd.Usage = D3D11_USAGE_DEFAULT;
		bd.ByteWidth = (UINT)( sizeof(DWORD) * m_nIndexCount );
		bd.BindFlags = D3D11_BIND_INDEX_BUFFER;
		bd.CPUAccessFlags = 0;
		InitData.pSysMem = m_pIndices;
		V_RETURN( SPGetD3D11Device()->CreateBuffer(&bd, &InitData, &m_pIndexBuffer) );

		return S_OK;
	}

	HRESULT SPStaticMesh::Create( SPOBJLoader* pOBJLoader )
	{
		HRESULT hr = S_OK;

		if ( pOBJLoader == NULL )
			return -1;

		std::vector <SPOBJLoader::VERTEX>* Vertices = pOBJLoader->GetVertices();
		std::vector <DWORD>* Indices = pOBJLoader->GetIndices();
		std::vector <DWORD>* Attributes = pOBJLoader->GetAttributes();
		std::vector <SPOBJLoader::Material*>* Materials = pOBJLoader->GetMaterial();

		m_pVertices = new SPOBJLoader::VERTEX[Vertices->size()];
		for ( UINT i = 0; i < Vertices->size(); i++ )
			m_pVertices[i] = Vertices->at(i);
		m_nVertexCount = Vertices->size();

		m_pIndices = new DWORD[Indices->size()];
		for ( UINT i = 0; i < Indices->size(); i++ )
			m_pIndices[i] = Indices->at(i);
		m_nIndexCount = Indices->size();

		/*
		// VB
		D3D11_BUFFER_DESC bd;
		ZeroMemory( &bd, sizeof(bd) );
		bd.ByteWidth = (UINT)( sizeof(SPOBJLoader::VERTEX) * Vertices->size() );
		bd.Usage = D3D11_USAGE_DEFAULT;
		bd.BindFlags = D3D11_BIND_VERTEX_BUFFER;
		bd.CPUAccessFlags = 0;
		bd.MiscFlags = 0;
		D3D11_SUBRESOURCE_DATA InitData;
		ZeroMemory( &InitData, sizeof(InitData) );
		InitData.pSysMem = m_pVertices;
		V_RETURN( pDevice->CreateBuffer(&bd, &InitData, &m_pVertexBuffer) );

		// IB
		bd.Usage = D3D11_USAGE_DEFAULT;
		bd.ByteWidth = (UINT)( sizeof(DWORD) * Indices->size() );
		bd.BindFlags = D3D11_BIND_INDEX_BUFFER;
		bd.CPUAccessFlags = 0;
		InitData.pSysMem = m_pIndices;
		V_RETURN( pDevice->CreateBuffer(&bd, &InitData, &m_pIndexBuffer) );
		*/
		CreateBuffer();

		// Subsets
		SP_SM_SUBSET* pSubset = NULL;
		for ( UINT iMaterial = 0; iMaterial < Attributes->size(); iMaterial++ )
		{
			if ( m_Subsets.size() > 0 && Attributes->at(iMaterial) == m_Subsets.back()->MaterialID )
			{
				m_Subsets.back()->IndexCount += 3;
			}
			else
			{
				pSubset = new SP_SM_SUBSET();
				pSubset->IndexCount = 3;
				pSubset->IndexStart = iMaterial * 3;
				pSubset->VertexStart = 0;
				pSubset->MaterialID = Attributes->at(iMaterial);
				m_Subsets.push_back(pSubset);
			}
		}

		// Materials
		for ( std::vector<SPOBJLoader::Material*>::iterator i = Materials->begin(); i != Materials->end(); ++i )
		{
			SP_SM_MATERIAL* pNewMaterial = new SP_SM_MATERIAL();
			pNewMaterial->vAmbient = (*i)->vAmbient;
			pNewMaterial->vDiffuse = (*i)->vDiffuse;
			pNewMaterial->vSpecular = (*i)->vSpecular;
			pNewMaterial->fAlpha = (*i)->fAlpha;
			pNewMaterial->bSpecular = (*i)->bSpecular;
			if ( (*i)->strTexture[0] )
			{
				pNewMaterial->bTexture = true;
				V( D3DX11CreateTextureFromFile( SPGetD3D11Device(),
					(*i)->strTexture, NULL, NULL, &pNewMaterial->pTexture, NULL ) );
				V( D3DX11CreateShaderResourceViewFromFile( SPGetD3D11Device(), 
					(*i)->strTexture, NULL, NULL, &pNewMaterial->pTextureRV, NULL) );
			}
			else
			{
				pNewMaterial->bTexture = false;
				pNewMaterial->pTexture = NULL;
				pNewMaterial->pTextureRV = NULL;
			}
			m_Materials.push_back( pNewMaterial );
		}

		return S_OK;
	}

	void SPStaticMesh::CreateMaterialFileBuffer( SPFileBuffer* pFB )
	{		
		HRESULT hr;
		
		UINT numMaterial = m_Materials.size();	

		ID3DBlob** pBlob = new ID3DBlob*[numMaterial];
		UINT numbyte = sizeof( UINT );
		for ( UINT i = 0; i < numMaterial; i++ )
		{
			numbyte += sizeof( UINT );
			numbyte += sizeof( SP_SM_MATERIAL );				
			pBlob[i] = NULL;
			if ( m_Materials[i]->bTexture )
			{
				V( D3DX11SaveTextureToMemory( SPGetD3D11DeviceContext(), 
					m_Materials[i]->pTexture, D3DX11_IFF_DDS, &pBlob[i], NULL ) );

				numbyte += pBlob[i]->GetBufferSize();
			}
		}

		pFB->CreateBuffer( numbyte );
		BYTE* pBuf = pFB->pFileBuffer;

		CopyMemory( pBuf, &numMaterial, sizeof( UINT ) );
		pBuf += sizeof( UINT );

		for ( UINT i = 0; i < numMaterial; i++ )
		{
			if ( pBlob[i] )
			{
				UINT nBufferSize = pBlob[i]->GetBufferSize();
				CopyMemory( pBuf, &nBufferSize, sizeof( UINT ) ); 
				pBuf += sizeof( UINT );

				CopyMemory( pBuf, m_Materials[i], sizeof( SP_SM_MATERIAL ) );
				pBuf += sizeof( SP_SM_MATERIAL );

				CopyMemory( pBuf, pBlob[i]->GetBufferPointer(), nBufferSize );
				pBuf += nBufferSize;
			}
			else
			{
				UINT nBufferSize = 0;
				CopyMemory( pBuf, &nBufferSize, sizeof( UINT ) ); 
				pBuf += sizeof( UINT );

				CopyMemory( pBuf, m_Materials[i], sizeof( SP_SM_MATERIAL ) );
				pBuf += sizeof( SP_SM_MATERIAL );
			}
		}
	}

	void SPStaticMesh::CreateMaterialFromFileBuffer( BYTE* pBufPtr )
	{
		HRESULT hr;

		BYTE* pBuf = pBufPtr;
		UINT* numMaterial = ( UINT* )pBuf;
		pBuf += sizeof( UINT );

		for ( UINT i = 0; i < *numMaterial; i++ )
		{
			UINT* nBufferSize = ( UINT* )pBuf;
			pBuf += sizeof( UINT );

			SP_SM_MATERIAL* pMaterial = ( SP_SM_MATERIAL* )pBuf;
			pBuf += sizeof( SP_SM_MATERIAL );

			void* pTextureBuf = ( void* )pBuf;
			pBuf += *nBufferSize;

			SP_SM_MATERIAL* pNew = new SP_SM_MATERIAL();
			pNew->bSpecular = pMaterial->bSpecular;
			pNew->fAlpha = pMaterial->fAlpha;
			pNew->nShininess = pMaterial->nShininess;
			pNew->vAmbient = pMaterial->vAmbient;
			pNew->vDiffuse = pMaterial->vDiffuse;
			pNew->vSpecular = pMaterial->vSpecular;
			pNew->bTexture = pMaterial->bTexture;
			if ( pNew->bTexture )
			{
				V( D3DX11CreateTextureFromMemory( SPGetD3D11Device(), 
					pTextureBuf, *nBufferSize, NULL, NULL, &pNew->pTexture, NULL) );

				V( D3DX11CreateShaderResourceViewFromMemory( SPGetD3D11Device(), 
					pTextureBuf, *nBufferSize, NULL, NULL, &pNew->pTextureRV, NULL) );
			}
			else
			{
				pNew->pTexture = NULL;
				pNew->pTextureRV = NULL;
			}

	
			m_Materials.push_back( pNew );
		}
	}

	void SPStaticMesh::CreateFileBuffer( SPFileBuffer* pFB )
	{
		SPFileBuffer* pMaterialFB = new SPFileBuffer();
		CreateMaterialFileBuffer( pMaterialFB );

		UINT sizeofV = sizeof( SPOBJLoader::VERTEX ) * m_nVertexCount;
		UINT sizeofI = sizeof( DWORD ) * m_nIndexCount;
		UINT numSubset = m_Subsets.size();
		UINT sizeofSubset = sizeof( SP_SM_SUBSET ) * m_Subsets.size();
		//UINT sizeofMaterial = sizeof( SP_SM_MATERIAL ) * m_Materials.size();

		pFB->CreateBuffer( sizeof( UINT ) * 3 + sizeofV+ sizeofI + sizeofSubset + pMaterialFB->nNumByte );

		BYTE* pBuf = pFB->pFileBuffer;
		CopyMemory( pBuf, &m_nVertexCount, sizeof( UINT ) );
		pBuf += sizeof( UINT );

		CopyMemory( pBuf, &m_nIndexCount, sizeof( UINT ) );
		pBuf += sizeof( UINT );

		CopyMemory( pBuf, &numSubset, sizeof( UINT ) );
		pBuf += sizeof( UINT );

		// Vertices
		CopyMemory( pBuf, m_pVertices, sizeofV );
		pBuf += sizeofV;

		// Indices
		CopyMemory( pBuf, m_pIndices, sizeofI );
		pBuf += sizeofI;
		
		// Subsets
		for ( UINT i = 0; i < numSubset; i++ )
		{
			SP_SM_SUBSET* pSubset = m_Subsets.at(i);
			CopyMemory( pBuf, pSubset, sizeof( SP_SM_SUBSET ) );
			pBuf += sizeof( SP_SM_SUBSET );
		}

		// Materials
		CopyMemory( pBuf, pMaterialFB->pFileBuffer, pMaterialFB->nNumByte );

		SAFE_DELETE( pMaterialFB );
	}

	void SPStaticMesh::CreateFromFileBuffer( BYTE* pBufPtr )
	{
		UINT* p = (UINT*)( pBufPtr );
		m_nVertexCount = p[0];
		m_nIndexCount = p[1];
		UINT numSubset = p[2];

		m_pVertices = new SPOBJLoader::VERTEX[m_nVertexCount];
		m_pIndices = new DWORD[m_nIndexCount];
		UINT sizeofV = sizeof( SPOBJLoader::VERTEX ) * m_nVertexCount;
		UINT sizeofI = sizeof( DWORD ) * m_nIndexCount;

		BYTE* pBuf = pBufPtr + sizeof( UINT ) * 3;

		// Vertices
		CopyMemory( m_pVertices, pBuf, sizeofV );
		pBuf += sizeofV;

		// Indices
		CopyMemory( m_pIndices, pBuf, sizeofI );
		pBuf += sizeofI;

		// subset
		SP_SM_SUBSET* pSubsets = (SP_SM_SUBSET*)pBuf;
		for ( UINT i = 0; i < numSubset; i++ )
		{
			SP_SM_SUBSET* pNewSubset = new SP_SM_SUBSET();
			/*
			pNewSubset->IndexCount = pSubsets[i].IndexCount;
			pNewSubset->IndexStart = pSubsets[i].IndexStart;
			pNewSubset->MaterialID = pSubsets[i].MaterialID;
			pNewSubset->VertexStart = pSubsets[i].VertexStart;
			*/
			CopyMemory( pNewSubset, pSubsets + i, sizeof( SP_SM_SUBSET ) );
			m_Subsets.push_back( pNewSubset );
		}
		pBuf += sizeof( SP_SM_SUBSET ) * numSubset;

		// material
		CreateMaterialFromFileBuffer( pBuf );

		// 
		CreateBuffer();
	}

}

///------------------------------------------------------------
// SPEmitterMeshRenderer
///------------------------------------------------------------
namespace SP
{
	class SPEmitterMeshRenderer : public SPD3DObject
	{
		friend void SPRenderEmitterMesh(  SPEmitterMesh*  );
	private:
		SPEmitterMeshRenderer()
		{
			Create();
		}

		struct CB_PRE_FRAME
		{
			float fAlpha;
			XMFLOAT3 vDiffuse;
			XMFLOAT3 vAmbient;
			XMFLOAT3 vLightColor;
			XMFLOAT3 vLightPosition;

			XMMATRIX mWorld;
			XMMATRIX mWorldViewProj;
		};

		struct CB_VS_PER_OBJECT
		{
			XMMATRIX mWorldViewProj;
			XMMATRIX mWorld;
		};

		struct CB_PS_PER_OBJECT
		{
			XMFLOAT4 m_vObjectColor;
		};

		struct CB_PS_PER_FRAME
		{
			XMFLOAT4 m_vLightDirAmbient;
		};

	private:
		void				Render( SPEmitterMesh* );
		HRESULT		Create();

		ID3D11InputLayout*			m_pVertexLayout;
		ID3D11VertexShader*		m_pVertexShader;
		ID3D11PixelShader*			m_pPixelShader[4];

		ID3D11Buffer*					m_pcbVSPerObject;
		ID3D11Buffer*					m_pcbPSPerObject;
		ID3D11Buffer*					m_pcbPSPerFrame;
		ID3D11SamplerState*		m_pSamplerLinear;
	};

	void SPRenderEmitterMesh(  SPEmitterMesh* pEmitterMesh )
	{
		static SPEmitterMeshRenderer staticSPEMR;
		staticSPEMR.Render( pEmitterMesh );
	}

	HRESULT SPEmitterMeshRenderer::Create()
	{
		HRESULT hr;

		ID3DBlob* pVSBlob = NULL;
		ID3DBlob* pPSBlob1 = NULL;
		ID3DBlob* pPSBlob2 = NULL;
		ID3DBlob* pPSBlob3 = NULL;
		ID3DBlob* pPSBlob4 = NULL;

		// Create the shaders
		D3D_SHADER_MACRO defines1[] = { { "COLOR_ONLY", " " }, { "LIGHTING", " " }, { 0 } };
		D3D_SHADER_MACRO defines2[] = { { "COLOR_ONLY", " " }, { 0 } };
		D3D_SHADER_MACRO defines3[] = { { "LIGHTING", " " }, { 0 } };

		V_RETURN( SPGetRenderEnvironment()->CompileShaderFromFile( L"hlsl\\StaticMesh.hlsl", NULL, "VSMain", "vs_5_0", &pVSBlob ) );
		V_RETURN( SPGetRenderEnvironment()->CompileShaderFromFile( L"hlsl\\StaticMesh.hlsl", defines1, "PSMain", "ps_5_0", &pPSBlob1 ) );
		V_RETURN( SPGetRenderEnvironment()->CompileShaderFromFile( L"hlsl\\StaticMesh.hlsl", defines2, "PSMain", "ps_5_0", &pPSBlob2 ) );
		V_RETURN( SPGetRenderEnvironment()->CompileShaderFromFile( L"hlsl\\StaticMesh.hlsl", defines3, "PSMain", "ps_5_0", &pPSBlob3 ) );
		V_RETURN( SPGetRenderEnvironment()->CompileShaderFromFile( L"hlsl\\StaticMesh.hlsl", NULL, "PSMain", "ps_5_0", &pPSBlob4 ) );

		V_RETURN( m_pDevice->CreateVertexShader( pVSBlob->GetBufferPointer(  ), pVSBlob->GetBufferSize(  ), NULL, &m_pVertexShader ) );
		V_RETURN( m_pDevice->CreatePixelShader( pPSBlob1->GetBufferPointer(  ), pPSBlob1->GetBufferSize(  ), NULL, &m_pPixelShader[0] ) );
		V_RETURN( m_pDevice->CreatePixelShader( pPSBlob2->GetBufferPointer(  ), pPSBlob2->GetBufferSize(  ), NULL, &m_pPixelShader[1] ) );
		V_RETURN( m_pDevice->CreatePixelShader( pPSBlob3->GetBufferPointer(  ), pPSBlob3->GetBufferSize(  ), NULL, &m_pPixelShader[2] ) );
		V_RETURN( m_pDevice->CreatePixelShader( pPSBlob4->GetBufferPointer(  ), pPSBlob4->GetBufferSize(  ), NULL, &m_pPixelShader[3] ) );

		// Define the input layout
		D3D11_INPUT_ELEMENT_DESC layout[] =
		{
			{"POSITION", 0, DXGI_FORMAT_R32G32B32_FLOAT, 0, 0,  D3D11_INPUT_PER_VERTEX_DATA, 0},
			{"NORMAL",   0, DXGI_FORMAT_R32G32B32_FLOAT, 0, 12, D3D11_INPUT_PER_VERTEX_DATA, 0},
			{"TEXCOORD", 0, DXGI_FORMAT_R32G32_FLOAT,    0, 24, D3D11_INPUT_PER_VERTEX_DATA, 0},
		};
		UINT numElements = ARRAYSIZE( layout );
		// Create the input layout
		V_RETURN( m_pDevice->CreateInputLayout( layout, numElements, pVSBlob->GetBufferPointer(  ),
			pVSBlob->GetBufferSize(  ), &m_pVertexLayout ) );

		SAFE_RELEASE( pVSBlob );
		SAFE_RELEASE( pPSBlob1 );
		SAFE_RELEASE( pPSBlob2 );
		SAFE_RELEASE( pPSBlob3 );
		SAFE_RELEASE( pPSBlob4 );

		// Create the constant buffers
		D3D11_BUFFER_DESC Desc;
		Desc.Usage = D3D11_USAGE_DYNAMIC;
		Desc.BindFlags = D3D11_BIND_CONSTANT_BUFFER;
		Desc.CPUAccessFlags = D3D11_CPU_ACCESS_WRITE;
		Desc.MiscFlags = 0;

		Desc.ByteWidth = sizeof( CB_VS_PER_OBJECT );
		V_RETURN( m_pDevice->CreateBuffer( &Desc, NULL, &m_pcbVSPerObject ) );

		Desc.ByteWidth = sizeof( CB_PS_PER_OBJECT );
		V_RETURN( m_pDevice->CreateBuffer( &Desc, NULL, &m_pcbPSPerObject ) );

		Desc.ByteWidth = sizeof( CB_PS_PER_FRAME );
		V_RETURN( m_pDevice->CreateBuffer( &Desc, NULL, &m_pcbPSPerFrame ) );

		// Create the sample state
		D3D11_SAMPLER_DESC SamDesc;
		ZeroMemory( &SamDesc, sizeof( SamDesc ) );
		SamDesc.Filter = D3D11_FILTER_MIN_MAG_MIP_LINEAR;
		SamDesc.AddressU = D3D11_TEXTURE_ADDRESS_WRAP;
		SamDesc.AddressV = D3D11_TEXTURE_ADDRESS_WRAP;
		SamDesc.AddressW = D3D11_TEXTURE_ADDRESS_WRAP;
		SamDesc.MipLODBias = 0.0f;
		SamDesc.MaxAnisotropy = 1;
		SamDesc.ComparisonFunc = D3D11_COMPARISON_ALWAYS;
		SamDesc.BorderColor[0] = SamDesc.BorderColor[1] = SamDesc.BorderColor[2] = SamDesc.BorderColor[3] = 0;
		SamDesc.MinLOD = 0;
		SamDesc.MaxLOD = D3D11_FLOAT32_MAX;
		V_RETURN( m_pDevice->CreateSamplerState( &SamDesc, &m_pSamplerLinear ) );

		return hr;
	}

	void SPEmitterMeshRenderer::Render( SPEmitterMesh* pEmitterMesh )
	{
		HRESULT hr;
		SPStaticMesh* pStaticMesh = pEmitterMesh->m_pStaticMesh;
		SPCamera* pCamera = SPGetCamera();
		XMMATRIX mWorld = pEmitterMesh->GetTransformMatrix();
		XMMATRIX mWorldViewProj = pEmitterMesh->GetTransformMatrix() * pCamera->GetViewProjMtx();

		// Per frame cb update
		D3D11_MAPPED_SUBRESOURCE MappedResource;

		V( m_pImmediateContext->Map( m_pcbPSPerFrame, 0, D3D11_MAP_WRITE_DISCARD, 0, &MappedResource ) );
		CB_PS_PER_FRAME* pPerFrame = ( CB_PS_PER_FRAME* )MappedResource.pData;
		pPerFrame->m_vLightDirAmbient = XMFLOAT4( 1.0f, 1.0f, 1.0f , 0.3f );
		//pPerFrame->m_vLightDirAmbient = XMFLOAT4( pCamera->GetEyePt(  ).x, pCamera->GetEyePt(  ).y, pCamera->GetEyePt(  ).z, 1.0f );
		m_pImmediateContext->Unmap( m_pcbPSPerFrame, 0 );

		m_pImmediateContext->PSSetConstantBuffers( 1, 1, &m_pcbPSPerFrame );

		//Get the mesh
		//IA setup
		m_pImmediateContext->IASetInputLayout( m_pVertexLayout );
		UINT Strides[1];
		UINT Offsets[1];
		ID3D11Buffer* pVB[1];
		pVB[0] = pStaticMesh->GetVB(  );
		Strides[0] = pStaticMesh->GetVBStride(  );
		Offsets[0] = 0;
		m_pImmediateContext->IASetVertexBuffers( 0, 1, pVB, Strides, Offsets );
		m_pImmediateContext->IASetIndexBuffer( pStaticMesh->GetIB(  ), DXGI_FORMAT_R32_UINT, 0 );

		m_pImmediateContext->IASetPrimitiveTopology( D3D11_PRIMITIVE_TOPOLOGY_TRIANGLELIST );

		// Set the shaders
		m_pImmediateContext->VSSetShader( m_pVertexShader, NULL, 0 );

		// VS Per object
		V( m_pImmediateContext->Map( m_pcbVSPerObject, 0, D3D11_MAP_WRITE_DISCARD, 0, &MappedResource ) );
		CB_VS_PER_OBJECT* pVSPerObject = ( CB_VS_PER_OBJECT* )MappedResource.pData;
		pVSPerObject->mWorldViewProj = XMMatrixTranspose( mWorldViewProj );
		pVSPerObject->mWorld = XMMatrixTranspose( mWorld );
		m_pImmediateContext->Unmap( m_pcbVSPerObject, 0 );

		m_pImmediateContext->VSSetConstantBuffers( 0, 1, &m_pcbVSPerObject );

		// PS Per object
		V( m_pImmediateContext->Map( m_pcbPSPerObject, 0, D3D11_MAP_WRITE_DISCARD, 0, &MappedResource ) );
		CB_PS_PER_OBJECT* pPSPerObject = ( CB_PS_PER_OBJECT* )MappedResource.pData;
		XMStoreFloat4( &pPSPerObject->m_vObjectColor, pEmitterMesh->GetColourVector() );
		//pPSPerObject->m_vObjectColor = XMFLOAT4( 1.0f, 1.0f, 1.0f, 1.0f );
		m_pImmediateContext->Unmap( m_pcbPSPerObject, 0 );

		m_pImmediateContext->PSSetConstantBuffers( 0, 1, &m_pcbPSPerObject );

		//Render    
		std::vector <SPStaticMesh::SP_SM_SUBSET*>* pSubsets = pStaticMesh->GetSubsets(  );
		std::vector <SPStaticMesh::SP_SM_MATERIAL*>* pMaterials = pStaticMesh->GetMaterials(  );

		m_pImmediateContext->PSSetSamplers(  0, 1, &m_pSamplerLinear );

		float vBlendFactor[4] = { 0.0f, 0.0f, 0.0f, 0.0f };
		m_pImmediateContext->OMSetBlendState( pEmitterMesh->GetBlendState(), vBlendFactor, 0xFFFFFFFF );
		m_pImmediateContext->OMSetDepthStencilState( pEmitterMesh->GetDepthStencilState(), 0 );
		m_pImmediateContext->RSSetState( pEmitterMesh->GetRasterizerState() );

		bool bLighting = pEmitterMesh->m_bLighting;
		bool bColourOnly = !pEmitterMesh->m_bTextureMapping;
		ID3D11PixelShader* pPSColor = ( bLighting ) ? m_pPixelShader[0]: m_pPixelShader[1];
		ID3D11PixelShader* pPSTexture = ( bLighting ) ? m_pPixelShader[2] : m_pPixelShader[3];
		pPSTexture = ( bColourOnly ) ? pPSColor : pPSTexture;

		SPStaticMesh::SP_SM_SUBSET* pSubset = NULL;
		for ( UINT subset = 0; subset < pSubsets->size(  ); subset++ )
		{
			pSubset = pSubsets->at( subset );
			ID3D11ShaderResourceView* pTextureRV = pMaterials->at( pSubset->MaterialID )->pTextureRV;
			if ( pTextureRV )
			{
				m_pImmediateContext->PSSetShaderResources( 0, 1, &pTextureRV );
				m_pImmediateContext->PSSetShader( pPSTexture, NULL, 0 );
			}
			else
			{
				m_pImmediateContext->PSSetShader( pPSColor, NULL, 0 );
			}
			m_pImmediateContext->DrawIndexed( pSubset->IndexCount, pSubset->IndexStart, pSubset->VertexStart );
		}
	}
}

///------------------------------------------------------------
// SPEmitterMesh
///------------------------------------------------------------
namespace SP
{
	SPEmitterMesh::SPEmitterMesh()
	{
		m_pStaticMesh = NULL;
		m_bReleaseaResouce = true;
		m_bTextureMapping = true;
		m_bLighting = true;
	}

	SPEmitterMesh::SPEmitterMesh( SPEmitterMesh* archetype, bool bDeepCopy )
		: SPEmitter( archetype )
	{
		m_pStaticMesh = NULL;
		m_bReleaseaResouce = bDeepCopy;
		if ( m_bReleaseaResouce )
		{
			SPFileBuffer* pFB = new SPFileBuffer();
			archetype->m_pStaticMesh->CreateFileBuffer( pFB );
			m_pStaticMesh = new SPStaticMesh();
			m_pStaticMesh->CreateFromFileBuffer( pFB->pFileBuffer );
			SAFE_DELETE( pFB );
		}
		else
		{
			m_pStaticMesh = archetype->m_pStaticMesh;
		}

		m_bLighting = archetype->m_bLighting;
		m_bTextureMapping = archetype->m_bTextureMapping;
	}

	SPEmitterMesh::~SPEmitterMesh()
	{
		if ( m_bReleaseaResouce )
			SAFE_DELETE( m_pStaticMesh );
	}

	void SPEmitterMesh::Create( SPEmitterMesh* archetype, bool bDeepCopy /* = false */ )
	{
		SPEmitter::Create( archetype );

		m_pStaticMesh = NULL;
		m_bReleaseaResouce = bDeepCopy;
		if ( m_bReleaseaResouce )
		{
			SPFileBuffer* pFB = new SPFileBuffer();
			archetype->m_pStaticMesh->CreateFileBuffer( pFB );
			m_pStaticMesh = new SPStaticMesh();
			m_pStaticMesh->CreateFromFileBuffer( pFB->pFileBuffer );
			SAFE_DELETE( pFB );
		}
		else
		{
			m_pStaticMesh = archetype->m_pStaticMesh;
		}

		m_bLighting = archetype->m_bLighting;
		m_bTextureMapping = archetype->m_bTextureMapping;
	}

	void SPEmitterMesh::Create( const WCHAR* filename )
	{
		SPOBJLoader* pLoader = new SPOBJLoader( filename );

		SAFE_DELETE( m_pStaticMesh );
		m_pStaticMesh = new SPStaticMesh( pLoader );
		
		delete pLoader;
	}

	void SPEmitterMesh::Initialize()
	{
		SPEmitter::Initialize();
	}

	void SPEmitterMesh::Render()
	{
		if ( m_pStaticMesh )
			SPRenderEmitterMesh( this );
	}

	void SPEmitterMesh::Advance( float fElapsedTime )
	{
		SPEmitter::Advance( fElapsedTime );
	}

	void SPEmitterMesh::ClearModule()
	{
		SPEmitter::ClearModule();
	}

	void SPEmitterMesh::CreateFileBuffer( SPFileBuffer* pFB )
	{
		SPFileBuffer* pEmitterFB = new SPFileBuffer();
		SPEmitter::CreateFileBuffer( pEmitterFB );

		SPFileBuffer* pStaticMeshFB = new SPFileBuffer();
		m_pStaticMesh->CreateFileBuffer( pStaticMeshFB );

		UINT nBufferSize = sizeof( int ) * 2;
		nBufferSize += sizeof( EmitterMeshFileHead );
		nBufferSize += pEmitterFB->nNumByte + pStaticMeshFB->nNumByte;
		pFB->CreateBuffer( nBufferSize );

		BYTE* pBuf = pFB->pFileBuffer;
		
		CopyMemory( pBuf, &pEmitterFB->nNumByte, sizeof( int ) );
		pBuf += sizeof( int );

		CopyMemory( pBuf, &pStaticMeshFB->nNumByte, sizeof( int ) );
		pBuf += sizeof( int );

		EmitterMeshFileHead MeshHead;
		MeshHead.bLighting = m_bLighting;
		MeshHead.bTextureMapping = m_bTextureMapping;

		CopyMemory( pBuf, &MeshHead, sizeof( EmitterMeshFileHead ) );
		pBuf += sizeof( EmitterMeshFileHead );

		CopyMemory( pBuf, pEmitterFB->pFileBuffer, pEmitterFB->nNumByte );
		pBuf += pEmitterFB->nNumByte;

		CopyMemory( pBuf, pStaticMeshFB->pFileBuffer, pStaticMeshFB->nNumByte );
		pBuf += pStaticMeshFB->nNumByte;

		SAFE_DELETE( pEmitterFB );		
		SAFE_DELETE( pStaticMeshFB );
	}

	void SPEmitterMesh::CreateFromFileBuffer( BYTE* pBufPtr )
	{
		int* bufSize = ( int* )pBufPtr;
		pBufPtr += sizeof( int ) * 2;

		EmitterMeshFileHead* MeshHead = ( EmitterMeshFileHead* )pBufPtr; 
		this->m_bTextureMapping = MeshHead->bTextureMapping;
		this->m_bLighting = MeshHead->bLighting;
		pBufPtr += sizeof( EmitterMeshFileHead );

		SPEmitter::CreateFromFileBuffer( pBufPtr );

		m_pStaticMesh = new SPStaticMesh();
		m_pStaticMesh->CreateFromFileBuffer( pBufPtr+ bufSize[0] );
	}

}


